<?php

declare(strict_types=1);

namespace app\service\sys;

use app\model\AuthRole;
use app\model\AuthUserRole;
use app\model\User;
use app\model\UserAuth;
use app\model\UserProfile;
use InvalidArgumentException;
use think\Collection;
use think\facade\Db;
use think\Paginator;

class AccountService
{
    private const SYSTEM_ROLE_SLUGS = ['admin', 'support', 'system_admin', 'super_admin', 'service_operator'];

    /**
     * 账户列表分页，可按状态 / 关键字 / 角色过滤。
     */
    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $allowedRoleSlugs = $this->systemRoleSlugs();
        $roleIdFilter = null;
        if (!empty($filters['role_id'])) {
            $roleIdFilter = (int) $filters['role_id'];
            if (!$this->isAllowedRoleId($roleIdFilter)) {
                return $query->whereRaw('1=0')->paginate([
                    'list_rows' => $pageSize,
                    'page' => $page,
                ]);
            }
        }

        $query = User::with([
            'profile',
            'roles' => static function ($relation) use ($allowedRoleSlugs) {
                $relation->whereIn('slug', $allowedRoleSlugs)
                    ->field(['id', 'name', 'slug', 'scope_type', 'status']);
            },
        ])->order('id', 'desc');

        $query->whereExists(function ($subQuery) use ($allowedRoleSlugs, $roleIdFilter) {
            $subQuery->table(AuthUserRole::getTable())->alias('aur')
                ->join(AuthRole::getTable() . ' ar', 'ar.id = aur.role_id')
                ->whereColumn('aur.user_id', User::getTable() . '.id')
                ->where('ar.scope_type', 'system')
                ->whereIn('ar.slug', $allowedRoleSlugs);
            if ($roleIdFilter !== null) {
                $subQuery->where('ar.id', $roleIdFilter);
            }
        });

        if (isset($filters['status']) && $filters['status'] !== '') {
            $query->where('status', (int) $filters['status']);
        }

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(function ($subQuery) use ($keyword) {
                $subQuery->whereLike('phone', '%' . $keyword . '%')
                    ->whereOr('email', 'like', '%' . $keyword . '%')
                    ->whereOrExists(function ($profileQuery) use ($keyword) {
                        $profileQuery->name(UserProfile::getTable())
                            ->whereColumn(UserProfile::getTable() . '.user_id', User::getTable() . '.id')
                            ->where(function ($nested) use ($keyword) {
                                $nested->whereLike('nickname', '%' . $keyword . '%')
                                    ->whereOr('real_name', 'like', '%' . $keyword . '%');
                            })
                            ->whereNull(UserProfile::getTable() . '.delete_time');
                    });
            });
        }

        return $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);
    }

    public function findById(int $id): ?User
    {
        $allowedRoleSlugs = $this->systemRoleSlugs();
        return User::with([
            'profile',
            'roles' => static function ($relation) use ($allowedRoleSlugs) {
                $relation->whereIn('slug', $allowedRoleSlugs)
                    ->field(['id', 'name', 'slug', 'scope_type', 'status']);
            },
        ])->find($id);
    }

    public function create(array $payload): User
    {
        $phone = trim((string) ($payload['phone'] ?? ''));
        $email = trim((string) ($payload['email'] ?? ''));
        $password = (string) ($payload['password'] ?? '');
        $status = isset($payload['status']) ? (int) $payload['status'] : 1;
        $nickname = trim((string) ($payload['nickname'] ?? ''));
        $realName = trim((string) ($payload['real_name'] ?? ''));
        $roleIds = $this->normalizeIds($payload['role_ids'] ?? []);

        if ($phone === '') {
            throw new InvalidArgumentException('手机号不能为空');
        }

        if ($password === '' || strlen($password) < 6) {
            throw new InvalidArgumentException('密码长度至少 6 位');
        }

        $this->assertPhoneUnique($phone);
        if ($email !== '') {
            $this->assertEmailUnique($email);
        }

        if (!in_array($status, [0, 1, 2], true)) {
            $status = 1;
        }

        if ($roleIds === [] || $roleIds === null) {
            throw new InvalidArgumentException('请至少选择一个系统角色');
        }

        $passwordHash = password_hash($password, PASSWORD_BCRYPT);

        return Db::transaction(function () use (
            $phone,
            $email,
            $passwordHash,
            $status,
            $nickname,
            $realName,
            $roleIds
        ) {
            /** @var User $user */
            $user = User::create([
                'phone' => $phone,
                'email' => $email !== '' ? $email : null,
                'password_hash' => $passwordHash,
                'status' => $status,
                'register_channel' => 'system',
            ]);

            $this->saveProfile($user, $nickname, $realName);
            $this->syncAuthRecords($user, $phone, $passwordHash, true);
            $this->syncRoles($user, $roleIds);

            return $this->refreshUser($user);
        });
    }

    public function update(User $user, array $payload): User
    {
        $phone = array_key_exists('phone', $payload) ? trim((string) $payload['phone']) : $user->phone;
        $email = array_key_exists('email', $payload) ? trim((string) $payload['email']) : (string) $user->email;
        $status = array_key_exists('status', $payload) ? (int) $payload['status'] : (int) $user->status;
        $password = $payload['password'] ?? null;
        $nickname = array_key_exists('nickname', $payload) ? trim((string) $payload['nickname']) : null;
        $realName = array_key_exists('real_name', $payload) ? trim((string) $payload['real_name']) : null;
        $roleIds = $this->normalizeIds($payload['role_ids'] ?? null);

        if ($phone === '') {
            throw new InvalidArgumentException('手机号不能为空');
        }

        $this->assertPhoneUnique($phone, (int) $user->id);
        if ($email !== '') {
            $this->assertEmailUnique($email, (int) $user->id);
        }

        $passwordHash = null;
        $passwordProvided = false;
        if ($password !== null && $password !== '') {
            if (strlen((string) $password) < 6) {
                throw new InvalidArgumentException('密码长度至少 6 位');
            }
            $passwordHash = password_hash((string) $password, PASSWORD_BCRYPT);
            $passwordProvided = true;
        }

        if (!in_array($status, [0, 1, 2], true)) {
            $status = (int) $user->status;
        }

        return Db::transaction(function () use (
            $user,
            $phone,
            $email,
            $status,
            $passwordHash,
            $passwordProvided,
            $nickname,
            $realName,
            $roleIds
        ) {
            $user->save([
                'phone' => $phone,
                'email' => $email !== '' ? $email : null,
                'status' => $status,
                'password_hash' => $passwordHash ?? $user->password_hash,
            ]);

        if ($nickname !== null || $realName !== null) {
            $currentProfile = $user->profile;
            $this->saveProfile(
                $user,
                $nickname ?? ($currentProfile->nickname ?? ''),
                $realName ?? ($currentProfile->real_name ?? '')
            );
        }

        $this->syncAuthRecords($user, $phone, $passwordHash, $passwordProvided);
        if ($roleIds !== null) {
            if ($roleIds === []) {
                throw new InvalidArgumentException('请至少选择一个系统角色');
            }
            $this->syncRoles($user, $roleIds);
        }

            return $this->refreshUser($user);
        });
    }

    public function delete(User $user): bool
    {
        return (bool) Db::transaction(function () use ($user) {
            $user->roles()->detach();
            UserAuth::where('user_id', $user->id)->delete();
            UserProfile::where('user_id', $user->id)->delete();

            return $user->delete();
        });
    }

    public function format(User $user): array
    {
        $profile = $user->profile;
        $roles = $user->roles instanceof Collection
            ? $user->roles
            : Collection::make($user->roles ?? []);

        return [
            'id' => (int) $user->id,
            'phone' => $user->phone,
            'email' => $user->email,
            'status' => (int) $user->status,
            'register_channel' => $user->register_channel,
            'last_login_time' => $user->last_login_time,
            'create_time' => $user->create_time,
            'update_time' => $user->update_time,
            'user_type' => $user->user_type ?? '',
            'profile' => [
                'nickname' => $profile->nickname ?? '',
                'real_name' => $profile->real_name ?? '',
                'avatar' => $profile->avatar ?? '',
            ],
            'roles' => array_values($roles->map(static function ($role) {
                return [
                    'id' => (int) $role->id,
                    'name' => $role->name,
                    'slug' => $role->slug,
                    'scope_type' => $role->scope_type,
                    'status' => (int) $role->status,
                ];
            })->toArray()),
        ];
    }

    public function formatCollection(iterable $users): array
    {
        $results = [];
        foreach ($users as $user) {
            if ($user instanceof User) {
                $results[] = $this->format($user);
            }
        }

        return $results;
    }

    private function saveProfile(User $user, string $nickname, string $realName): void
    {
        $profile = $user->profile;
        if ($profile) {
            $profile->save([
                'nickname' => $nickname !== '' ? $nickname : ($profile->nickname ?? ''),
                'real_name' => $realName !== '' ? $realName : ($profile->real_name ?? ''),
            ]);
            return;
        }

        UserProfile::create([
            'user_id' => $user->id,
            'nickname' => $nickname !== '' ? $nickname : ($user->phone ? mb_substr($user->phone, -4) : ''),
            'real_name' => $realName,
        ]);
    }

    private function syncRoles(User $user, array $roleIds): void
    {
        $roleIds = array_values(array_unique(array_map('intval', $roleIds)));
        $roleIds = array_filter($roleIds, static fn(int $id) => $id > 0);

        $validRoleIds = $roleIds === []
            ? []
            : array_values(
                AuthRole::whereIn('id', $roleIds)
                    ->whereIn('slug', $this->systemRoleSlugs())
                    ->column('id')
            );

        if (count($validRoleIds) !== count($roleIds)) {
            throw new InvalidArgumentException('存在无效的系统角色');
        }

        $relation = $user->roles();
        $relation->detach();

        if ($validRoleIds === []) {
            return;
        }

        $relation->attach($validRoleIds, ['status' => 1]);
    }

    private function syncAuthRecords(User $user, string $phone, ?string $passwordHash, bool $passwordProvided): void
    {
        $this->upsertAuthRecord($user, UserAuth::TYPE_SMS, $phone, null);

        if ($passwordProvided || $passwordHash !== null) {
            $this->upsertAuthRecord($user, UserAuth::TYPE_PASSWORD, $phone, $passwordHash);
        } else {
            $passwordRecord = UserAuth::withTrashed()
                ->where('user_id', $user->id)
                ->where('identity_type', UserAuth::TYPE_PASSWORD)
                ->find();

            if ($passwordRecord) {
                if ($passwordRecord->identifier !== $phone) {
                    $passwordRecord->identifier = $phone;
                }
                if ($passwordRecord->delete_time) {
                    $passwordRecord->restore();
                }
                $passwordRecord->save();
            }
        }
    }

    private function upsertAuthRecord(User $user, string $identityType, string $identifier, ?string $credential): void
    {
        $record = UserAuth::withTrashed()
            ->where('user_id', $user->id)
            ->where('identity_type', $identityType)
            ->find();

        if ($record) {
            $record->identifier = $identifier;
            if ($credential !== null) {
                $record->credential = $credential;
            }
            if ($record->delete_time) {
                $record->restore();
            }
            $record->status = 1;
            $record->save();
            return;
        }

        UserAuth::create([
            'user_id' => $user->id,
            'identity_type' => $identityType,
            'identifier' => $identifier,
            'credential' => $credential,
            'status' => 1,
        ]);
    }

    private function normalizeIds(mixed $ids): ?array
    {
        if ($ids === null) {
            return null;
        }

        if (!is_array($ids)) {
            throw new InvalidArgumentException('角色参数格式不正确');
        }

        $converted = array_values(array_unique(array_map('intval', $ids)));

        return array_values(array_filter($converted, static fn(int $id) => $id > 0));
    }

    private function assertPhoneUnique(string $phone, ?int $excludeId = null): void
    {
        $query = User::where('phone', $phone);
        if ($excludeId !== null) {
            $query->where('id', '<>', $excludeId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('手机号已存在');
        }
    }

    private function assertEmailUnique(string $email, ?int $excludeId = null): void
    {
        if ($email === '') {
            return;
        }

        $query = User::where('email', $email);
        if ($excludeId !== null) {
            $query->where('id', '<>', $excludeId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('邮箱已存在');
        }
    }

    private function refreshUser(User $user): User
    {
        /** @var User $fresh */
        $fresh = $this->findById((int) $user->id);
        return $fresh;
    }

    private function isAllowedRoleId(int $roleId): bool
    {
        if ($roleId <= 0) {
            return false;
        }

        return AuthRole::where('id', $roleId)
            ->whereIn('slug', $this->systemRoleSlugs())
            ->count() > 0;
    }

    private function systemRoleSlugs(): array
    {
        return self::SYSTEM_ROLE_SLUGS;
    }
}
